#include "BTree.h"
#include "NodoDatos.h"
#include "NodoIndice.h"
#include "RegistroIndice.h"


BTree::BTree(std::string nomArch, bool nuevoArbol) {

	bool exito = false;

	if (!nuevoArbol){
		// Inicializa el archivo
		exito = this->_arch.abrirArch(this->_cabecera, nomArch);
	}

	// Si no puede abrirlo, crea uno nuevo
	if (!exito){
		// Primero hay que inicializar la cabecera
		this->_cabecera.setAlturaArbol(0);
		this->_cabecera.setCantBloques(2);	// Solo la cabecera y la raiz
		this->_cabecera.setPosBloqueRaiz(1);
		this->_cabecera.setPosBloqueSecuencial(1);

		this->_arch.crearArch(this->_cabecera, nomArch);

		// Guarda una raiz vacia
		NodoDatos nd;
		this->_arch.guardar(nd, this->_cabecera.getPosBloqueRaiz());
	}
}


BTree::~BTree(){
	this->_arch.guardar(this->_cabecera, 0);
}


RegistroDatos BTree::buscarRegistro(unsigned int id) {
	// Carga la raiz
	INodo* raiz = cargarNodo(this->_cabecera.getPosBloqueRaiz(), this->_cabecera.getAlturaArbol());

	// Por comodidad, el id que queremos encontrar
	RegistroDatos rBuscar(id, "");
	RegistroDatos reg;

	if ( this->_cabecera.getAlturaArbol() == 0) {
		reg = raiz->buscarRegistroPorId(rBuscar, true);

	} else {
		reg = buscarRecursivo(rBuscar, raiz, this->_cabecera.getAlturaArbol()-1);
	}

	delete raiz;

	return reg;
}


RegistroDatos BTree::buscarRecursivo(RegistroDatos regId, INodo* nodo, int altura){

	RegistroIndice rIndex = nodo->buscarRegistroPorId(regId, false);
	INodo* n = cargarNodo(rIndex.getPosHijo(), altura);

	RegistroDatos rd;
	if (altura == 0){
		rd = n->buscarRegistroPorId(regId, true);
	} else {
		rd = buscarRecursivo(regId, n, altura-1);
	}

	delete n;

	return rd;
}


Resultado BTree::insertarRegistro(RegistroDatos* reg){
	// Carga la raiz del árbol
	INodo* raiz = cargarNodo(this->_cabecera.getPosBloqueRaiz(), this->_cabecera.getAlturaArbol());

	Resultado resul;
	if (this->_cabecera.getAlturaArbol() == 0){
		resul = raiz->agregarRegistro(reg);
	} else {
		resul = insertarRegistroRecursivo(reg, raiz, this->_cabecera.getAlturaArbol()-1);
	}

	// Manejar el resultado de la inserción
	if (resul == MODIFICADO || resul == UNDERFLOW){
		// Guarda la raiz modificada y la cabecera actualizada
		guardarNodo(raiz, this->_cabecera.getPosBloqueRaiz());
		this->_arch.guardar(this->_cabecera, 0);
		resul = OK;

	} else if (resul == OVERFLOW){
		// Se crea una nueva raiz y se inserta un registro con datos invalidos para que funcione
		// el algoritmo de resolver overflow
		INodo* newRaiz = new NodoIndice();
		RegistroIndice* rRaiz = new RegistroIndice(IRegistro::GET_MAXIMO_IMPOSIBLE(), -1);
		newRaiz->agregarRegistro(rRaiz);

		// Resuelve el overflow de la raiz
		resolverOverflow(raiz, newRaiz, *rRaiz, this->_cabecera.getAlturaArbol());

		// Se guarda la nueva raiz
		guardarNodo(newRaiz, this->_cabecera.getPosBloqueRaiz());

		// Si la altura del arbol era "0" el primer bloque de datos cambiará de posicion
		if (this->_cabecera.getAlturaArbol() == 0){
			// Hay que buscar la posicion del primer nodo de datos
			RegistroIndice r(0, -1);	// No puede existir un registro menor
			r = newRaiz->buscarRegistroPorId(r, false);
			this->_cabecera.setPosBloqueSecuencial(r.getPosHijo());

			// El primer bloque de datos no volverá a cambiar de posición, según este algoritmo
		}

		// Aumenta la altura del árbol
		this->_cabecera.setAlturaArbol(this->_cabecera.getAlturaArbol()+1);

		// Guarda los datos de la cabecera para mantenerla acualizada
		this->_arch.guardar(this->_cabecera, 0);

		delete newRaiz;
		resul = OK;
	}

	// El resultado
	delete raiz;
	return resul;
}

Resultado BTree::insertarRegistroRecursivo(RegistroDatos* reg, INodo* nPadre, int altura){

	// Busca el registro y carga el nodo apuntado por el mismo
	RegistroIndice r = nPadre->buscarRegistroPorId(*reg, false);
	INodo* nActual = cargarNodo(r.getPosHijo(), altura);

	Resultado resul;
	if (altura == 0){
		resul = nActual->agregarRegistro(reg);
	} else {
		resul = insertarRegistroRecursivo(reg, nActual, altura-1);
	}

	// Maneja el resultado de la inserción
	if (resul == MODIFICADO){
		guardarNodo(nActual, r.getPosHijo());
		resul = OK;

	} else if (resul == OVERFLOW){
		resul = resolverOverflow(nActual, nPadre, r, altura);
	}

	delete nActual;
	return resul;
}


// Lee desde el disco el nodo en la posicion correspondiente
INodo* BTree::cargarNodo(int posBloque, int altura){

	// Según la altura donde nos encontramos leyendo es el tipo de nodo
	INodo* n;
	if (altura == 0) n = new NodoDatos();
	else n = new NodoIndice();

	// Lee el nodo desde el archivo
	this->_arch.leer(*n, posBloque);

	return n;
}


// Guarda el nodo en el archivo, si no se indica posición se guarda en un bloque libre
// o uno nuevo si es que no hay libres, devuelve la posición donde se guardó
int BTree::guardarNodo(INodo* n, int posBloque){
	if (posBloque == -1){
		// Se esta agregando un nuevo bloque
		this->_cabecera.setCantBloques(this->_cabecera.getCantBloques()+1);
	}
	return this->_arch.guardar(*n, posBloque);
}


// Resuelve la situación de sobreflujo de los nodos del árbol
Resultado BTree::resolverOverflow(INodo* nHijo, INodo* nPadre, RegistroIndice rPadre, int altura){

	INodo* nNuevo;
	if (altura == 0) nNuevo = new NodoDatos();
	else nNuevo = new NodoIndice();

	// El registro que devuelve el split contiene el separdor entre "nHijo" y "nNuevo"
	// TODO: Hay que hacer delete de esto!? mucho no me gusta...
	IRegistro& rClave = nHijo->splitNodo(*nNuevo);

	// Si es un overflow de hojas hay que enlazar los nodos
	if (altura == 0){
		int pos = static_cast<NodoDatos*>(nHijo)->getNextNodo();
		static_cast<NodoDatos*>(nNuevo)->setNextNodo(pos);
	}

	// Guarda el nuevo nodo
	int posNuevo = guardarNodo(nNuevo);
	delete nNuevo;	// Ya no lo necesito

	// Si es un overflow de hojas hay que enlazar los nodos
	if (altura == 0){
		static_cast<NodoDatos*>(nHijo)->setNextNodo(posNuevo);
	}

	// Guarda el nodo actualizado,
	// Se guarda la "pos" por si el guardar resulta en un nuevo bloque
	int posHijo = guardarNodo(nHijo, rPadre.getPosHijo());

	// Los nuevos registros que hay que agregar al padre
	IRegistro* r1 = new RegistroIndice(rClave.getClave(), posHijo);	// FIXME: Parece que el delete de nNuevo libera este registro tambien...
	IRegistro* r2 = new RegistroIndice(rPadre.getClave(), posNuevo);

	// Quita la anterior clave maxima y guarda las nuevas
	nPadre->modificarRegistroPorId(r2);
	Resultado resul = nPadre->agregarRegistro(r1);

	// Devuelve el resultado de la ultima operación de inserción por si hay que informar algo
	delete &rClave;
	return resul;
}

